﻿using System;
using System.Collections.Generic;
using System.Drawing;
using MentalAlchemy.Atomics;
using MentalAlchemy.Molecules.Image.Struct;

namespace MentalAlchemy.Molecules.Image
{
	public class Generic
	{
		#region - Image file checking. -
		/// <summary>
		/// [molecule]
		/// 
		/// Method to define whether given file name is a supported image filename.
		/// </summary>
		/// <param name="filename">Input filename.</param>
		/// <returns>[True] if filename is an image file and [False] otherwise.</returns>
		public static bool IsImageFile(string filename)
		{
			string strExt = filename.Substring(filename.LastIndexOf('.') + 1);
			strExt = strExt.ToUpper();
			if (Array.IndexOf(Const.ImageExtensions, strExt) >= 0) { return true; }
			return false;
		}
		#endregion

		#region - Validation. -
		/// <summary>
		/// [molecule]
		/// 
		/// Validates pixels intensity value using saturation maths.
		/// </summary>
		/// <param name="value">Input value.</param>
		/// <returns>Correct pixel intensity value.</returns>
		public static byte ValidateIntensityValue(int value)
		{
			int temp = value;
			if (temp < Const.GRAYLEVEL_MIN)
			{
				temp = Const.GRAYLEVEL_MIN;
			}
			else if (temp > Const.GRAYLEVEL_MAX)
			{
				temp = Const.GRAYLEVEL_MAX;
			}
			return (byte)temp;
		} 
		#endregion

		#region - Convolution. -
		/// <summary>
		/// [molecule]
		/// 
		/// Convolves given 2d array [data] with [kernel]
		/// </summary>
		/// <param name="data">Input 2d array.</param>
		/// <param name="kernel">Kernel for convolution.</param>
		/// <returns>Convolution result.</returns>
		public static byte[,] Convolve(byte[,] data, int[,] kernel)
		{
			int kernelH = kernel.GetLength(0), kernelW = kernel.GetLength(1);
			if (kernelH != kernelW)
			{
				throw new Exception("[Generic :: Convolve] exception: Can't convolve with a non-square kernel.");
			}

			var res = new byte[data.GetLength(0), data.GetLength(1)];

			// implement convolution.
			int height = data.GetLength(0), width = data.GetLength(1);
			int kernelSize = kernel.GetLength(0) / 2;
			for (int i = 0; i < height; ++i)
			{
				for (int j = 0; j < width; ++j)
				{
					// convolve at pixel at the i-th row and j-th column.

					int left = Math.Max(j - kernelSize, 0),
						right = Math.Min(j + kernelSize, width - 1),
						top = Math.Max(i - kernelSize, 0),
						bottom = Math.Min(i + kernelSize, height - 1);
					int convValue = 0;
					for (int i1 = top; i1 <= bottom; ++i1)
					{
						for (int j1 = left; j1 <= right; ++j1)
						{
							int x = j1 - j + kernelSize, y = i1 - i + kernelSize;
							convValue += data[i1, j1] * kernel[y, x];
						}
					}

					res[i, j] = ValidateIntensityValue(convValue);
				}
			}

			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Convolve given 2d array [data] with [kernel]
		/// </summary>
		/// <param name="data">Input 2d array.</param>
		/// <param name="kernel">Kernel for convolution.</param>
		/// <returns>Convolution result.</returns>
		public static byte[,] Convolve(byte[,] data, float[,] kernel)
		{
			int kernelH = kernel.GetLength(0), kernelW = kernel.GetLength(1);
			if (kernelH != kernelW)
			{
				throw new Exception("[Generic :: Convolve] exception: Can't convolve with a non-square kernel.");
			}

			var res = new byte[data.GetLength(0), data.GetLength(1)];

			// todo: check this method.
			// implement convolution.
			int height = data.GetLength(0), width = data.GetLength(1);
			int kernelSize = kernel.GetLength(0) / 2;
			for (int i = 0; i < height; ++i)
			{
				for (int j = 0; j < width; ++j)
				{
					// convolve at pixel at the i-th row and j-th column.

					int left = Math.Max(j - kernelSize, 0),
						right = Math.Min(j + kernelSize, width - 1),
						top = Math.Max(i - kernelSize, 0),
						bottom = Math.Min(i + kernelSize, height - 1);
					float convValue = 0.0f;
					for (int i1 = top; i1 <= bottom; ++i1)
					{
						for (int j1 = left; j1 <= right; ++j1)
						{
							int x = j1 - j + kernelSize, y = i1 - i + kernelSize;
							convValue += data[i1, j1] * kernel[y, x];
						}
					}

					res[i, j] = ValidateIntensityValue((int)convValue);
				}
			}

			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Not using parallelization.
		/// 
		/// Convolve given 2d array [img] with [kernel]
		/// </summary>
		/// <param name="table">Input 2d array.</param>
		/// <param name="kernel">Kernel for convolution.</param>
		/// <returns>Convolution result.</returns>
		public static float[,] ConvolveEx(float[,] table, float[,] kernel)
		{
			int kernelH = kernel.GetLength(0), kernelW = kernel.GetLength(1);
			if (kernelH != kernelW)
			{
				throw new Exception("[Generic :: Convolve] exception: Can't convolve with a non-square kernel.");
			}

			int kernelSize = kernel.GetLength(0) / 2;
			int height = table.GetLength(0), width = table.GetLength(1);
			float[,] wTable = MatrixMath.CreateBorders(table, kernelSize, kernelSize);
			float[,] res = new float[height, width];

			// implement convolution.
			for (int i = kernelSize; i < height+kernelSize; ++i)
			{
				for (int j = kernelSize; j < width+kernelSize; ++j)
				{
					// convolve at pixel at the i-th row and j-th column.

					int left = j - kernelSize,
						right = j + kernelSize,
						top = i - kernelSize,
						bottom = i + kernelSize;
					var convValue = 0.0f;
					for (int i1 = top; i1 <= bottom; ++i1)
					{
						int x = 0, y = i1 - top;
						for (int j1 = left; j1 <= right; ++j1, ++x)
						{
							convValue += wTable[i1, j1] * kernel[y, x];
						}
					}

					res[i - kernelSize, j - kernelSize] = ValidateIntensityValue((int)convValue);
				}
			}

			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Calculates convolution for the given table and kernel using borders extension and OpenMP.
		/// </summary>
		/// <param name="table">Input 2d array.</param>
		/// <param name="kernel">Kernel for convolution.</param>
		/// <returns>Convolution result.</returns>
		public static float[,] Convolve(float[,] table, float[,] kernel)
		{
			var resTable = MentalAlchemy.Performance.ImageMaths.Convolve(table, kernel);
			return resTable;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Computes convolution and adds offset to the convolution result.
		/// </summary>
		/// <param name="img">Input 2d array.</param>
		/// <param name="kernel">Kernel for convolution.</param>
		/// <param name="offset">Offset value.</param>
		/// <returns>Convolution result.</returns>
		public static byte[,] ConvolveOffset(byte[,] img, float[,] kernel, byte offset)
		{
			int kernelH = kernel.GetLength(0), kernelW = kernel.GetLength(1);
			if (kernelH != kernelW)
			{
				throw new Exception("[Generic :: Convolve] exception: Can't convolve with a non-square kernel.");
			}

			var res = new byte[img.GetLength(0), img.GetLength(1)];

			// todo: check this method.
			// implement convolution.
			int height = img.GetLength(0), width = img.GetLength(1);
			int kernelSize = kernel.GetLength(0) / 2;
			for (int i = 0; i < height; ++i)
			{
				for (int j = 0; j < width; ++j)
				{
					// convolve at pixel at the i-th row and j-th column.

					int left = Math.Max(j - kernelSize, 0),
						right = Math.Min(j + kernelSize, width - 1),
						top = Math.Max(i - kernelSize, 0),
						bottom = Math.Min(i + kernelSize, height - 1);
					float convValue = 0.0f;
					for (int i1 = top; i1 <= bottom; ++i1)
					{
						for (int j1 = left; j1 <= right; ++j1)
						{
							int x = j1 - j + kernelSize, y = i1 - i + kernelSize;
							convValue += img[i1, j1] * kernel[y, x];
						}
					}

					res[i, j] = ValidateIntensityValue((int)convValue + offset);
				}
			}

			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Computes convolution and adds offset to the convolution result.
		/// </summary>
		/// <param name="img">Input 2d array.</param>
		/// <param name="kernel">Kernel for convolution.</param>
		/// <param name="offset">Offset value.</param>
		/// <returns>Convolution result.</returns>
		public static float[,] ConvolveOffset(float[,] img, float[,] kernel, byte offset)
		{
			int kernelH = kernel.GetLength(0), kernelW = kernel.GetLength(1);
			if (kernelH != kernelW)
			{
				throw new Exception("[Generic :: Convolve] exception: Can't convolve with a non-square kernel.");
			}

			var res = new float[img.GetLength(0), img.GetLength(1)];

			// todo: check this method.
			// implement convolution.
			int height = img.GetLength(0), width = img.GetLength(1);
			int kernelSize = kernel.GetLength(0) / 2;
			for (int i = 0; i < height; ++i)
			{
				for (int j = 0; j < width; ++j)
				{
					// convolve at pixel at the i-th row and j-th column.

					int left = Math.Max(j - kernelSize, 0),
						right = Math.Min(j + kernelSize, width - 1),
						top = Math.Max(i - kernelSize, 0),
						bottom = Math.Min(i + kernelSize, height - 1);
					float convValue = 0.0f;
					for (int i1 = top; i1 <= bottom; ++i1)
					{
						for (int j1 = left; j1 <= right; ++j1)
						{
							int x = j1 - j + kernelSize, y = i1 - i + kernelSize;
							convValue += img[i1, j1] * kernel[y, x];
						}
					}

					res[i, j] = convValue + offset;
				}
			}

			return res;
		}
		#endregion

		#region - Markers. -
		/// <summary>
		/// [molecule]
		/// 
		/// Places cicular markers on the given image.
		/// </summary>
		/// <param name="img">Input image.</param>
		/// <param name="markers">List of circular markers.</param>
		/// <returns>Image with markers.</returns>
		public static Bitmap SetMarkers (Bitmap img, List<CircleMarker> markers)
		{
			var res = new Bitmap(img);

			var g = Graphics.FromImage(res);
			var pen = new Pen(Color.Green, 1.0f);

			foreach (var marker in markers)
			{
				int size = marker.radius*2 + 1;
				g.DrawEllipse(pen, marker.point.X, marker.point.Y, size, size);
			}
			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Returns markers denoting [table] entries with values exceeding [t] and with radius [radius]
		/// </summary>
		/// <param name="table">Input table of values.</param>
		/// <param name="t">Threshold to select entries.</param>
		/// <param name="radius">Markers radius.</param>
		/// <returns>List of found markers.</returns>
		public static List<CircleMarker> GetMarkers (float[,] table, float t, int radius)
		{
			int width = table.GetLength(1), height = table.GetLength(0);
			var pts = new List<CircleMarker>();
			for (int i = 0; i < height; ++i)
			{
				for (int j = 0; j < width; ++j)
				{
					if (table[i, j] > t)
					{
						var pt = new CircleMarker();
						pt.point = new Point(j, i);
						pt.radius = radius;
						pts.Add(pt);
					}
				}
			}
			return pts;
		}

		#endregion

		#region - Points. -
		/// <summary>
		/// [molecule]
		/// 
		/// Returns list of points with coordinates of entries which exceed the given threshold value.
		/// </summary>
		/// <param name="table">Input image intensity table.</param>
		/// <param name="t">Threshold value.</param>
		/// <returns>List of pixels coordinates, which intensity is more than a threshold.</returns>
		public static List<Point> GetPoints (float[,] table, float t)
		{
			int width = table.GetLength(1), height = table.GetLength(0);
			var pts = new List<Point>();
			for (int i = 0; i < height; ++i)
			{
				for (int j = 0; j < width; ++j)
				{
					if (table[i, j] > t)
					{
						pts.Add(new Point(j, i));
					}
				}
			}
			return pts;
		}
		#endregion

		#region - Color fill. -
		/// <summary>
		/// [molecule]
		/// 
		/// Fills specified rows of the given image with the prescribed color.
		/// </summary>
		/// <param name="img">Input image.</param>
		/// <param name="color">Color to fill.</param>
		/// <param name="start">Starting row index.</param>
		/// <param name="finish">Ending row index (not colored!).</param>
        public static void FillRows (Bitmap img, Color color, int start, int finish)
        {
			int width = img.Width;
        	for (int i=start; i<finish; ++i)
        	{
        		for (int j=0; j<width; ++j)
        		{
        			img.SetPixel(j, i, color);
        		}
        	}
        }
		#endregion
	}
}
